﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections;
using System.Reflection;
using WcfContrib.Utils;

namespace WcfContrib.Internal
{
    static class ObjectDumperUtil
    {
        public static string DumpToString(this object element)
        {
            return element.DumpToString(0);
        }

        public static string DumpToString(this object element, int depth)
        {
            StringBuilder sb = new StringBuilder();
            using (TextWriter tw = new StringWriter(sb))
            {
                element.DumpToWriter(depth, tw);
                tw.Close();
            }
            return sb.ToString();
        }

        public static object DumpToConsole(this object element)
        {
            element.DumpToConsole(0);

            return element;
        }

        public static object DumpToConsole(this object element, int depth)
        {
            element.DumpToWriter(depth, Console.Out);

            return element;
        }

        public static object DumpToWriter(this object element, TextWriter writer)
        {
            element.DumpToWriter(0, writer);

            return element;
        }

        public static object DumpToWriter(this object element, int depth, TextWriter writer)
        {
            ObjectDumper.Instance.WriteObject(element, writer, null, depth);

            return element;
        }
    }

    class ObjectDumper : Singleton<ObjectDumper>
    {
        private void Write(TextWriter writer, string s, ref int pos)
        {
            if (s != null)
            {
                writer.Write(s);
                pos += s.Length;
            }
        }

        private void WriteLine(TextWriter writer, ref int pos)
        {
            this.WriteLine(writer, null, ref pos);
        }

        private void WriteLine(TextWriter writer, string s, ref int pos)
        {
            this.Write(writer, s, ref pos);
            writer.WriteLine();
            pos = 0;
        }

        private void WriteIndent(TextWriter writer, int level, ref int pos)
        {
            for (int i = 0; i < level; i++)
                this.Write(writer, "  ", ref pos);
        }

        private void WriteTab(TextWriter writer, ref int pos)
        {
            this.Write(writer, "  ", ref pos);
            while (pos % 8 != 0)
                this.Write(writer, " ", ref pos);
        }

        internal void WriteObject(object element, TextWriter writer, string prefix, int depth)
        {
            int pos = 0;
            this.WriteObject(element, writer, prefix, depth, 0, ref pos);
        }

        private void WriteObject(object element, TextWriter writer, string prefix, int depth, int level, ref int pos)
        {
            if (element == null || element is ValueType || element is string)
            {
                this.WriteIndent(writer, level, ref pos);
                this.Write(writer, prefix, ref pos);
                this.WriteValue(writer, element, ref pos);
                this.WriteLine(writer, ref pos);
            }
            else
            {
                IEnumerable enumerableElement = element as IEnumerable;
                if (enumerableElement != null)
                {
                    foreach (object item in enumerableElement)
                    {
                        if (item is IEnumerable && !(item is string))
                        {
                            this.WriteIndent(writer, level, ref pos);
                            this.Write(writer, prefix, ref pos);
                            this.WriteLine(writer, "...", ref pos);
                            if (level < depth)
                            {
                                this.WriteObject(item, writer, prefix, depth - 1, level + 1, ref pos);
                            }
                        }
                        else
                        {
                            this.WriteObject(item, writer, prefix, depth - 1, level, ref pos);
                        }
                    }
                }
                else
                {
                    MemberInfo[] members = element.GetType().GetMembers(BindingFlags.Public | BindingFlags.Instance);
                    this.WriteIndent(writer, level, ref pos);
                    this.Write(writer, prefix, ref pos);
                    bool propWritten = false;
                    foreach (MemberInfo m in members)
                    {
                        FieldInfo f = m as FieldInfo;
                        PropertyInfo p = m as PropertyInfo;
                        if (f != null || p != null)
                        {
                            if (propWritten)
                            {
                                this.WriteTab(writer, ref pos);
                            }
                            else
                            {
                                propWritten = true;
                            }
                            this.Write(writer, m.Name, ref pos);
                            this.Write(writer, "=", ref pos);
                            Type t = f != null ? f.FieldType : p.PropertyType;
                            if (t.IsValueType || t == typeof(string))
                            {
                                object value = f != null ? f.GetValue(element) : p.GetValue(element, null);
                                this.WriteValue(writer, value, ref pos);
                            }
                            else
                            {
                                if (typeof(IEnumerable).IsAssignableFrom(t))
                                {
                                    this.Write(writer, "...", ref pos);
                                }
                                else
                                {
                                    this.Write(writer, "{ }", ref pos);
                                }
                            }
                        }
                    }

                    if (propWritten)
                        this.WriteLine(writer, ref pos);

                    if (level < depth)
                    {
                        foreach (MemberInfo m in members)
                        {
                            FieldInfo f = m as FieldInfo;
                            PropertyInfo p = m as PropertyInfo;
                            if (f != null || p != null)
                            {
                                Type t = f != null ? f.FieldType : p.PropertyType;
                                if (!(t.IsValueType || t == typeof(string)))
                                {
                                    object value = f != null ? f.GetValue(element) : p.GetValue(element, null);
                                    if (value != null)
                                    {
                                        this.WriteObject(value, writer, m.Name + ": ", depth - 1, level + 1, ref pos);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        private void WriteValue(TextWriter writer, object o, ref int pos)
        {
            if (o == null)
            {
                this.Write(writer, "null", ref pos);
            }
            else if (o is DateTime)
            {
                this.Write(writer, ((DateTime)o).ToShortDateString(), ref pos);
            }
            else if (o is ValueType || o is string)
            {
                this.Write(writer, o.ToString(), ref pos);
            }
            else if (o is IEnumerable)
            {
                this.Write(writer, "...", ref pos);
            }
            else
            {
                this.Write(writer, "{ }", ref pos);
            }
        }
    }
}
